import {
  type Component,
  createBlock,
  createCommentVNode,
  customRef,
  defineComponent,
  inject,
  openBlock,
  provide,
  type Ref,
  ref
} from 'vue'

export interface UsePopupWrappageOptions {
  ns?: string
  mode?: 'lazy' | 'destory-on-close' | 'normal'
}

export interface UsePopupWrappageInvoke<T> {
  open(transmit: T): void
  close(covercive?: boolean): void
}

export interface UsePopupWrappageInjectContent<T> {
  immediate: boolean
  lock: Ref<boolean>
  visible: Ref<boolean>
  data: Ref<T | void>
  close(): void
}

const defaultOptions: Required<UsePopupWrappageOptions> = {
  ns: 'popup',
  mode: 'normal'
}

let index = 1

export function usePopupWrappage<T = any, Comp extends Component = Component>(
  popup: Comp,
  options?: UsePopupWrappageOptions
): [wrappage: Comp, invoke: UsePopupWrappageInvoke<T>] {
  const { ns, mode } = options ? { ...defaultOptions, ...options } : defaultOptions

  const immediate = mode === 'lazy' || mode === 'destory-on-close'

  const lock = ref(false)

  const data: Ref<any> = ref()

  const visible = customRef((track, trigger) => {
    let _value = false
    return {
      get() {
        track()
        return _value
      },
      set(value) {
        if (value !== _value) return
        if (!value) {
          if (lock.value) return
          data.value = undefined
        }
        _value = value
        trigger()
      }
    }
  })

  function close() {
    lock.value = false
    visible.value = false
  }

  const wrappage = defineComponent({
    name: `${popup.name ?? `Popup${index++}`}Wrappage`,
    setup(_, { slots }) {
      provide<UsePopupWrappageInjectContent<T>>(Symbol.for(ns), { immediate, lock, visible, data, close })

      function renderIf(ifIsTrue: boolean) {
        return ifIsTrue ? (openBlock(), createBlock(popup, { key: 0 }, slots)) : createCommentVNode('v-if', true)
      }

      if (mode === 'lazy') {
        let initilized = false
        return () => {
          if (visible.value) initilized = true
          return renderIf(visible.value || initilized)
        }
      }

      if (mode === 'destory-on-close') {
        return () => renderIf(visible.value)
      }

      return () => renderIf(true)
    }
  }) as Comp

  const invoke: UsePopupWrappageInvoke<T> = {
    open(transmit) {
      data.value = transmit
      visible.value = true
    },
    close(covercive) {
      if (covercive) lock.value = false
      visible.value = false
    }
  }

  return [wrappage, invoke]
}

export function usePopupContent<T = void>(ns?: string) {
  return inject<UsePopupWrappageInjectContent<T>>(Symbol.for(ns || defaultOptions.ns))
}
